原本我們的即時數據儀表板就像一個簡潔的咖啡店櫃台 - 只顯示今天賣了多少杯咖啡(orders)。老闆每天早上都會滿意地看著螢幕上跳動的數字,點點頭說:「嗯,生意不錯!」
但是有一天,老闆突然皺起眉頭:「等等,我想知道這些訂單裡到底賣了什麼?是拿鐵多還是美式多?大杯的比較受歡迎還是小杯的?」
這就是我們面臨的挑戰 - 從簡單的訂單數量,進化到需要訂單明細(orders_detail)的複雜分析。
重要提醒
本文所有程式碼皆為教學與概念演示用的 Pseudo Code,可以幫助你理解並解決實際場景的問題,但這裡的程式碼不能直接使用,主要目的是用來講解 Stream Processing 的設計思路與核心概念。閱讀時建議把重點放在理解整體架構和設計邏輯上,程式碼細節部分可以輕鬆看過就好。
還記得我們之前討論的 Lambda 架構嗎?
我們之前的文章重點在加速層如何快速處理即時數據,現在面臨的問題是:當老闆需要更複雜的分析時,該在哪一層實現JOIN?
想像一下高速公路的收費站 - 如果每輛車都要停下來做複雜的檢查和登記,整個交通就會大塞車。加速層就像這個收費站,它的任務很單純:
關鍵原則:加速層不做 JOIN!
加速層只負責:簡單清洗 → 快速寫入 → 完成!
老闆打開儀表板,點擊「查看訂單詳情」。這時候,魔法發生在 Serving Layer:
SELECT
o.order_id,
o.customer_id,
o.order_time,
o.total_amount,
od.product_name,
od.quantity,
od.unit_price,
SUM(od.quantity * od.unit_price) as line_total
FROM
orders AS o
JOIN
orders_detail AS od ON o.order_id = od.order_id -- JOIN 操作在這裡執行
GROUP BY
o.order_id, od.product_name
ORDER BY
o.order_time DESC;
這個架構設計看似簡單,但真正的考驗在於:你的 SQL 功力夠強嗎?
當老闆的需求越來越複雜時,你就會發現這條路有多崎嶇:
老闆說:「我要看每個產品在不同時段的銷售趨勢,還要按客戶類型分群!」
-- 複雜的多表 JOIN + 分析查詢
WITH hourly_product_sales AS (
SELECT
od.product_name,
DATE_FORMAT(o.order_time, '%H') as hour_of_day,
c.customer_type,
SUM(od.quantity) as qty_sold,
SUM(od.quantity * od.unit_price) as revenue,
COUNT(DISTINCT o.customer_id) as unique_customers
FROM
orders o
JOIN
orders_detail od ON o.order_id = od.order_id
JOIN
customers c ON o.customer_id = c.customer_id
WHERE
o.order_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY
od.product_name, hour_of_day, c.customer_type
),
product_rankings AS (
SELECT
product_name,
hour_of_day,
customer_type,
qty_sold,
revenue,
unique_customers,
ROW_NUMBER() OVER (
PARTITION BY hour_of_day, customer_type
ORDER BY revenue DESC
) as sales_rank
FROM hourly_product_sales
)
SELECT
pr.product_name,
pr.hour_of_day,
pr.customer_type,
pr.revenue,
pr.sales_rank,
ROUND(pr.revenue / SUM(pr.revenue) OVER (
PARTITION BY pr.hour_of_day, pr.customer_type
) * 100, 2) as revenue_percentage
FROM
product_rankings pr
WHERE
pr.sales_rank <= 5
ORDER BY
pr.hour_of_day,
pr.customer_type,
pr.sales_rank;
這還只是開胃菜!真正的 OLAP 報表通常包含:
選擇這個架構,你需要掌握:
這個故事反映了很多團隊在實作 Lambda 架構時的常見做法:在加速層保持簡單,把複雜邏輯推遲到資料庫層處理
這種「先存後 JOIN」的策略是一把雙面劍:
現在老闆能看到複雜的分析結果,但背後是工程師在深夜與複雜SQL搏鬥的身影…
在效能調優的過程中,你很可能會發現 - - JOIN 是最消耗資料庫效能的操作之一。
數據倉庫領域有個常見解法:大寬表(Wide Table),用預先展開的方式,把查詢需要的欄位全部攤平成單張表,避免查詢時再 JOIN。
這種思路不只在離線數倉可行,在即時數據處理中也能用。如果我們能在 Streaming 流程中提前把多張表 Join 起來,直接生成即時大寬表,那麼下游查詢就能變得更快、更輕量,減少 OLAP 查詢的壓力。
下一篇,我們就來看看,如何在 Streaming 中設計即時大寬表,減少 Serving Layer 的 JOIN 開銷,並分析它帶來的好處與取捨。